/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file db.c
 * pyhp database abstraction support
 */

#include <apr_strings.h>

#include "pyhp_layer.h"
#include "db.h"
#include "db/pyhp_mysql.h"

#include "logging.h"


/**
 * Python function to connect to the database.
 * Retrieves the connection from the cache if available or calls
 * the db backend function to create one.
 *
 * @param url The url of the db, depends from the backend
 * @param user The user for which to connect to the db
 * @param pass The password of the user
 *
 * @return a database connection id
 */
static PyObject *pyhp_db_connect(PyObject *self, PyObject *args)
{
    PyObject *url = NULL;
    PyObject *user = NULL;
    PyObject *pass = NULL;

    char *c_url = "";
    char *c_user = "";
    char *c_pass = "";

    PyObject *db_cache = PyDict_GetItemString(self, "__db_cache");
    char *db_cache_key = NULL;
    PyObject *db_connection = NULL;
    PyObject *db_type = NULL;
    PyObject *py_db_cache_key = NULL;
    PyObject *result = NULL;

    PyObject *pool = PyDict_GetItemString(self, "apache_pool");
    apr_pool_t *p = PyCObject_AsVoidPtr(pool);

    if (!PyArg_ParseTuple(args, "OOO", &url, &user, &pass))
        return NULL;

    c_url = PyString_AsString(url);
    if (user != Py_None)
        c_user = PyString_AsString(user);
    if (pass != Py_None)
        c_pass = PyString_AsString(pass);

    db_cache_key = apr_pstrcat(p, c_url, c_user, c_pass, NULL);
    result = PyDict_GetItemString(db_cache, db_cache_key);

    if (!result) {
        result = PyDict_New();

        if (strcasestr(c_url, "mysql:") == c_url) {
            db_connection = pyhp_mysql_connect(c_url, c_user, c_pass);
            db_type = PyString_FromString("mysql");
        }
        else
            result = Py_None;

        if (result != Py_None) {
            PyDict_SetItemString(result, "connection", db_connection);
            Py_XDECREF(db_connection);

            PyDict_SetItemString(result, "type", db_type);
            Py_XDECREF(db_type);

            py_db_cache_key = PyString_FromString(db_cache_key);
            PyDict_SetItemString(result, "cache_key", py_db_cache_key);
            Py_XDECREF(py_db_cache_key);
        }

        PyDict_SetItemString(db_cache, db_cache_key, result);
        Py_XDECREF(result);
    }

    Py_INCREF(result);
    return result;
}

/**
 * Python function to execute an SQL statement
 *
 * @param connection The connection for which to execute the query
 * @param statement The statement to execute
 *
 * @return the number of modified rows if the statement was successfull
 * or None on failure
 */
static PyObject *pyhp_db_exec(PyObject *self, PyObject *args)
{
    PyObject *connection = NULL;
    PyObject *statement = NULL;

    if ( !PyArg_ParseTuple(args, "OO", &connection, &statement) )
        return NULL;

    PyObject *connection_type = PyDict_GetItemString(connection, "type");
    PyObject *connection_db = PyDict_GetItemString(connection, "connection");

    if (connection_db == Py_None) {
        Py_INCREF(Py_None);
        return Py_None;
    }

    if ( strcasestr(PyString_AsString(connection_type), "mysql") != NULL ) {
        return pyhp_mysql_exec(PyCObject_AsVoidPtr(connection_db), statement);
    }
    else {
        Py_INCREF(Py_None);
        return Py_None;
    }
}

/**
 * Python function to execute an SQL query
 *
 * @param connection The connection for which to execute the query
 * @param statement The statement to execute
 *
 * @return an instance of the QueryResult class of the backend or None on failure
 */
static PyObject *pyhp_db_query(PyObject *self, PyObject *args)
{
    PyObject *connection = NULL;
    PyObject *statement = NULL;

    if ( !PyArg_ParseTuple(args, "OO", &connection, &statement) )
        return NULL;

    PyObject *connection_type = PyDict_GetItemString(connection, "type");
    PyObject *connection_db = PyDict_GetItemString(connection, "connection");

    if (connection_db == Py_None) {
        Py_INCREF(Py_None);
        return Py_None;
    }

    if ( strcasestr(PyString_AsString(connection_type), "mysql") != NULL ) {
        return pyhp_mysql_query(PyCObject_AsVoidPtr(connection_db), statement);
    }
    else {
        Py_INCREF(Py_None);
        return Py_None;
    }
}

/**
 * Python function to close a db connection.
 * _Forces_ the database closure also removing it from the cache, this
 * should not be called unless you really want to force the db closure.
 *
 * @param connection The connection to close
 *
 * @return None
 */
static PyObject *pyhp_db_close(PyObject *self, PyObject *args)
{
    PyObject *connection = NULL;
    PyObject *db_cache = PyDict_GetItemString(self, "__db_cache");
    PyObject *db_cache_key = NULL;

    if(!PyArg_ParseTuple(args, "O", &connection))
        return NULL;

    db_cache_key = PyDict_GetItemString(connection, "cache_key");
    if (PyDict_GetItem(db_cache, db_cache_key)) {
        PyObject *connection_type = PyDict_GetItemString(connection, "type");
        PyObject *connection_db = PyDict_GetItemString(connection, "connection");

        if (connection_db == Py_None) {
            Py_INCREF(Py_None);
            return Py_None;
        }

        if ( strcasestr(PyString_AsString(connection_type), "mysql") != NULL ) {
            pyhp_mysql_close(PyCObject_AsVoidPtr(connection_db));
        }

        PyDict_DelItem(db_cache, db_cache_key);
    }

    Py_INCREF(Py_None);
    return Py_None;
}


static PyMethodDef database_method_table[] = {
    { "connect", (PyCFunction)pyhp_db_connect, METH_VARARGS, "Connects to a database" },
    { "execute", (PyCFunction)pyhp_db_exec, METH_VARARGS, "Executes an SQL statement" },
    { "query", (PyCFunction)pyhp_db_query, METH_VARARGS, "Executes an SQL query and returns the result" },
    { "close", (PyCFunction)pyhp_db_close, METH_VARARGS, "Forced Closing of a database connection" },
    { NULL, NULL }
};

/**
 * Exports the db module to the python layer
 * @see pyhp_layer_init
 */
void pyhp_db_export(PyObject * dict, PyObject * self)
{
    PyObject *db = Py_InitModule4("pyhp_db", database_method_table,
                                     "", self, PYTHON_API_VERSION );
    PyDict_SetItemString(dict, "db", db );
    Py_XDECREF(db);

    dict = PyModule_GetDict(db);
    PyDict_SetItemString(dict, "__internals", self);
    Py_XDECREF(self);
    Py_INCREF(dict);

    PyObject * db_cache = PyDict_New();
    PyDict_SetItemString(self, "__db_cache", db_cache);
    Py_XDECREF(db_cache);
}

/* C Layer accessor functions */

/**
 * Interface to pyhp_db_query for using database from the module itself.
 * @see pyhp_db_query
 */
PyObject *pyhp_cdb_query(PyHPData *pydata, PyObject *connection, char const *query)
{
    PyObject *self = pyhp_layer_get_object(pydata, "__internals");
    PyObject *args = Py_BuildValue("(Os)", connection, query);
    return pyhp_db_query(self, args);
}

/**
 * Interface to pyhp_db_exec for using database from the module itself.
 * @see pyhp_db_exec
 */
int pyhp_cdb_exec(PyHPData *pydata, PyObject *connection, char const *query)
{
    PyObject *self = pyhp_layer_get_object(pydata, "__internals");
    PyObject *args = Py_BuildValue("(Os)", connection, query);
    PyObject *res = pyhp_db_exec(self, args);

    int c_res = PyLong_AsLong(res);
    Py_XDECREF(res);

    return c_res;
}

/**
 * Interface to pyhp_db_connect for using database from the module itself.
 * @see pyhp_db_connect
 */
PyObject *pyhp_cdb_connect(PyHPData *pydata, char const * url, char const *user, char const *pass)
{
    PyObject *args = Py_BuildValue("(sss)", url, user, pass);
    PyObject *self = pyhp_layer_get_object(pydata, "__internals");

    return pyhp_db_connect(self, args);
}

/**
 * Interface get number of rows resulting from a query from C
 * Must be called after performing pyhp_cdb_result_fetchall or the result
 * will be wrong.
 */
int pyhp_cdb_result_rcount(PyObject *qresult)
{
    PyObject *res = pyhp_mysql_rcount((struct pyhp_mysql_result*)qresult, NULL);
    int c_res = PyLong_AsLong(res);

    if (res == Py_None)
        c_res = 0;

    Py_XDECREF(res);
    return c_res;
}

/**
 * Interface to get results of a query from C
 * It will return a PyList of PyDicts
 */
PyObject *pyhp_cdb_result_fetchall(PyObject *qresult)
{
    return pyhp_mysql_fetch_all((struct pyhp_mysql_result*)qresult, NULL);
}

